%==========================================================================
%=  This file is part of the Sound Restoration Project
%=  (c) Copyright Interdisciplinary Mathematics Institute
%=                University of South Carolina, Department of Mathematics
%=  ALL RIGHTS RESERVED
%=
%=  Author: Borislav Karaivanov
%==========================================================================

%==========================================================================
% List of the files on which this procedure depends:
%
% none
%
%==========================================================================

%==========================================================================
% Below "separated ranges" means ranges such that for any two of them there
% is an integer that does not belong to either of them but is between them.
% For instance, (3:5) and (7:10) are separated because 6 is between them
% but (2:4) and (5:8) are not because there is no integer between 4 and 5.
%==========================================================================
% The function "getFileSequenceFromRepresentative" takes the name
% (including a full directory path) of a single file and finds all files
% residing in the same directory whose names have the same prefix and
% extension but a different index. Among them the function returns only
% those with indices within am allowed range, or, even more restrictively,
% only those with indices in the maximal run including the given file
% restricted to an allowed range.
% INPUT: "inFileName" is a string specifying a file name (including a full
% directory path).
% "allowedRange" is an optional row vector of length two holding
% non-negative integers. These two integers specify a range of indices
% outside of which files do not need to be considered.
% "areGapsAllowed" is an optional boolean parameter specifying whether gaps
% in the found file sequence are to be considered acceptable or only a
% single maximal run is to be returned. By default gaps are not allowed,
% i.e., only a single maximal run is to be returned.
% OUTPUT: "inDirPath" returns the full directory path to the given file.
% "inFileNamePrefixNoPath" returns the prefix portion of the name of the
% given file, i.e., the given name less the full directory path, index
% field, and file extension.
% "indFieldLength" returns the length of the index field of the given file.
% "inFileExt" returns the extension of the given file.
% "fileRangeArr" returns a collection of separated ranges of indices
% corresponding to the files matching the given template. It returns a 2D
% array of two columns and many rows. Each row represents a range of
% integers. The ranges are separated (see above) and listed in increasing
% order of their starting points (or ending points for that matter). If
% gaps are allowed (see "areGapsAllowed") then all ranges of files matching
% the given template restricted to the allowed range are returned. If gaps
% are not allowed only the maximal run including the given file restricted
% to the allowed range is returned.
%==========================================================================
function [inDirPath, inFileNamePrefixNoPath, indFieldLength, inFileExt, fileRangeArr] = ...
    getFileSequenceFromRepresentative(inFileName, allowedRange, areGapsAllowed)

if (nargin < 2)
    allowedRange = [uint64(0), Inf];
end
if (nargin < 3)
    areGapsAllowed = false;
end

% Assert that the specified input file name is valid.
assert((isempty(inFileName) == false), ...
    'SoundRestoration:getFileSequenceFromRepresentative:EmptyInputFileName', ...
    'Input file name is empty; it needs to be a non-empty string specifying an existing file.');
assert((ischar(inFileName) == true), ...
    'SoundRestoration:getFileSequenceFromRepresentative:Not-A-StringInputFileName', ...
    'Input file name is not a string; it needs to be a non-empty string specifying an existing file.');
% Issue a warning in case the specified file does not exist.
if (exist(inFileName, 'file') ~= 2)
    warndlg(['The input file ' inFileName ' does not exists.'], 'Input file not found');
end

% Determine the input directory (i.e., the directory where the given input
% file resides), the file name and extension.
[inDirPath, inFileNameNoPath, inFileExt] = fileparts(inFileName);
inDirPath = [inDirPath filesep];

% Exclude the dot from the file extension.
inFileExt = inFileExt(2:end);

% Find all non-digits before the last dot.
nonDigitIndArr = regexp(inFileNameNoPath, '[^0-9]');
% Determine the length of the prefix (excluding the directory path).
if (isempty(nonDigitIndArr) == false)
    prefixLength = nonDigitIndArr(end);
else
    prefixLength = 0;
end
% Determine the prefix without the full directory path.
inFileNamePrefixNoPath = inFileNameNoPath(1:prefixLength);
% Determine the length of the index field.
indFieldLength = numel(inFileNameNoPath) - prefixLength;
indFieldIndArr = (prefixLength + 1):(prefixLength + indFieldLength);

% Find all files in the input directory and their number.
fileListing = dir(inDirPath);
numFiles = numel(fileListing);
% Extract only the file names.
fileNameCellArr = {fileListing.name};
clear fileListing;
% Sort the names in lexicographical order. This guarantees that the indices
% of the matching files will appear in increasing order.
fileNameCellArr = sort(fileNameCellArr);
% Allocate memory to store the ranges of indices of existing files.
fileRangeArr = zeros(numFiles, 2, 'uint64');

% Find the indices of all files residing in the input directory that have
% the same prefix and extension as the given input file name.
numFoundRanges = 0;
for k = 1:numFiles
    
    % Get the current file name.
    currFileName = fileNameCellArr{k};
    
    % If the current file name matches the pattern of the given input file
    % name, then store the current file's index.
    startInd = regexpi(currFileName, ['^' inFileNamePrefixNoPath '[0-9]{' num2str(indFieldLength) '}\.' inFileExt '$']);
    if (isempty(startInd) == false)
        % Get the current file's index.
        currInd = sscanf(currFileName(indFieldIndArr), '%lu');
        if ( (numFoundRanges > 0)&&(currInd == fileRangeArr(numFoundRanges, 2) + 1) )
            % If the previous file index is exactly one less than the
            % current index, then extend the current index range.
            fileRangeArr(numFoundRanges, 2) = currInd;
        else
            % If the previous file index is more than one less than the
            % current index, then create and initialize a new index range.
            numFoundRanges = numFoundRanges + 1;
            fileRangeArr(numFoundRanges, :) = currInd;
        end
    end
end

% Trim off any unused entries.
fileRangeArr = fileRangeArr(1:numFoundRanges, :);

%==========================================================================
% Note that the found ranges are separated and are stored in increasing
% order of their starting points (or ending points for that matter).
%==========================================================================
% Find the index of the first of the found ranges that is not entirely
% to the left of the allowed range.
firstHitInd = find(fileRangeArr(:, 2) >= allowedRange(1), 1, 'first');
% Find the index of the last of the found ranges that is not entirely
% to the right of the allowed range.
lastHitInd = find(fileRangeArr(:, 1) <= allowedRange(2), 1, 'last');

if ( (isempty(firstHitInd) == true)||(isempty(lastHitInd) == true)||(firstHitInd > lastHitInd) )
    % If either all found ranges are to the left or all found ranges are to
    % the right of the allowed range, then no file meets the requirements.
    % Likewise, if the entire allowed range fits between two adjacent of
    % the found ranges.
    fileRangeArr = [];
else
    % Some of the found ranges meet the allowed range:
    % Possibly reduce the first found range that meets the allowed range.
    fileRangeArr(firstHitInd, 1) = max(fileRangeArr(firstHitInd, 1), allowedRange(1));
    fileRangeArr(firstHitInd, 2) = min(fileRangeArr(firstHitInd, 2), allowedRange(2));
    % Possibly reduce the last found range that meets the allowed range.
    fileRangeArr(lastHitInd, 1) = max(fileRangeArr(lastHitInd, 1), allowedRange(1));
    fileRangeArr(lastHitInd, 2) = min(fileRangeArr(lastHitInd, 2), allowedRange(2));
    % Keep only the found ranges (possibly reduced) that meet the allowed
    % range.
    fileRangeArr = fileRangeArr(firstHitInd:lastHitInd, :);
end

if (areGapsAllowed == false)
    % Get the given template file's index.
    fileInd = sscanf(inFileNameNoPath(indFieldIndArr), '%lu');
    % Determine the range to which the given template file belongs.
    rangeInd = find((fileRangeArr(:, 1) <= fileInd)&(fileInd <= fileRangeArr(:, 2)));
    if (isempty(rangeInd) == false)
        fileRangeArr = fileRangeArr(rangeInd, :);
    else
        fileRangeArr = [];
    end
    
%     % Assert that there are no gaps in the file sequence.
%     assert((size(fileRangeArr, 1) <= 1), ...
%         'SoundRestoration:getFileSequenceFromRepresentative:GapsInFileSequence', ...
%         ['Gaps in the file sequence; the found ranges are: '  sprintf('(%d,%d), ', fileRangeArr)]);
end

% Compare the index fields of the first file in the first range and the
% last file in the last range to determine how many of the leading digits
% in the index fields of all files coincide.
firstFileIndStr = num2str(fileRangeArr(1, 1), ['%0' num2str(indFieldLength) 'd']);
lastFileIndStr = num2str(fileRangeArr(end, 2), ['%0' num2str(indFieldLength) 'd']);
% Find the number of coinciding leading digits in all index fields.
numCoinciding = find(firstFileIndStr ~= lastFileIndStr, 1, 'first') - 1;
% Make sure that the index field length is not greater than 6, unless
% necessary otherwise.
indFieldLength = min(indFieldLength, max(6, indFieldLength - numCoinciding));
inFileNamePrefixNoPath = inFileNameNoPath(1:(end - indFieldLength));

% Adjust the ranges, if necessary.
reductionAmountStr = firstFileIndStr(1:(numel(firstFileIndStr) - indFieldLength));
if (isempty(reductionAmountStr) == false)
    fileRangeArr = fileRangeArr - str2double(reductionAmountStr)*(10^indFieldLength);
end

return;
% end of the function "getFileSequenceFromRepresentative"
